/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package grex.fitnessfunctions;

import grex.Environment;
import grex.ErrorManagement.EvolutionListener;
import grex.EvolutionProcess;
import grex.fitnessfunctions.ErrorFunctions.IErrorFunction;
import grex.GP;
import grex.IPredictiveModel;
import grex.Options;
import grex.Data.Prediction;
import grex.Data.PredictionContainer;
import grex.Nodes.GeneException;
import java.io.Serializable;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;

/**
 *
 * @author RIK
 */
public abstract  class FitnessFunction implements IErrorFunction, EvolutionListener, Serializable{
    protected GP gp; //grants acces to the evaluated GP in subclasses.
    protected IPredictiveModel model; //grants acces to the evaluated predictiveModel in subclasses.
    protected Options options;
    protected PredictionContainer pc;
    protected String name;     
    protected Environment environment; 
    public static final String[] REGRESSION_FITNESS_FUNCTIONS = {"MAE_Fitness","RMSE_Fitness","MAPE_Fitness","MUAPE_Fitness", "CORR_Fitness"};
    public static final String[] CLASSIFICATION_FITNESS_FUNCTIONS = {"REX_Fitness", "ACC_Fitness","AUC_Fitness","BRI_Fitness", "BRE_Fitness"};
    
    public static final String TRAIN = "TRAIN", TEST = "TEST", VAL = "VAL";
    public static final String ACC="ACC", AUC="AUC",BRI="BRI",BRE="BRE",MAE="MAE",
            RMSE = "RMSE", MAPE="MAPE", WMAPE="WMAPE", MUAPE="MUAPE",LEN="LEN", CORR="R", ENTROPY="ENTROPY", GINI="GINI",SPEC="SPEC";
    private boolean alternatePressure = false; //under evaluation!
    
    private Double removeKey; //Key for deleting instances
    private PredictionContainer evaluationData; // buffer to that keeps the isntances that should be used for calculating error metrics.
    protected FitnessFunction(String name){
        this.name = name;
    }
    
    protected FitnessFunction(String name, double removeKey){
        this.name =name;
        this.removeKey = removeKey;
        
    }
    
    private void initGP(GP gp)throws GeneException{
        gp.getHead().notifyChildrenOfModification();
        gp.getHead().setModified(true);
        gp.train();
        this.gp=gp;
        options = gp.getOptions();
    }

    public double calcError(IPredictiveModel model, PredictionContainer pc) throws GeneException{
        this.model = model;
        this.pc = pc;
        if(model instanceof GP){
            gp = (GP) model;
            initGP(gp);
        }
        

        createEvaluationDataset();
        model.execute(this.pc);
        return calcFinalError(this.pc);
    }

    private synchronized void createEvaluationDataset(){
         if(removeKey != null){
            evaluationData = new PredictionContainer(pc.size());
            for(Prediction p:pc.values()){
                if(p.getInstance()[0]!=removeKey)
                    evaluationData.put(p.getInstance(), p);               
            }
            pc = evaluationData;
        }
    }
    
    public double calcError(PredictionContainer pc){
        this.pc=pc;
        
        createEvaluationDataset();        
        return calcFinalError(pc);
    }
    
    // Override this method if all instance is needed for calcualting the fitness, see AUCFitness or CorrelationFitness for an example.    
    protected double calcFinalError(PredictionContainer pc){
        this.pc=pc;
        return 100 * normalizeTotalError(calcTotalError(pc));
    }
    
    public double calcFitness(GP gp) throws GeneException{
        model = gp;
        this.gp=gp;
        gp.train();        
        options = gp.getOptions();
        this.pc=gp.getPcTrain();

        if(options.getPUNISHMENT_FOR_LENGTH()<0){
                    double n = pc.values().size();
                    double v = gp.getNrOfNodes();
                    double estimatedError = (n+v)/(n-v);
                    return calcFinalError(pc) * estimatedError;// 
        }
            
        return calcFinalError(pc) + calcParismonyPressure(gp);
    }
    

    //Calculates the total error using calcPredictionError (defined in the subclass)
    protected double calcTotalError(PredictionContainer pc){
        double totalError = 0;
        for(Prediction p:pc.values()){
            totalError += calcPredictionError(p,p.getTargetValue());
        }
        return totalError;
    }
    //Calculates the error for a single predictoin
    protected abstract double calcPredictionError(Prediction prediction, double targetValue);
    
    //Overide if dividing with number of prediction doesn't yeild a number between 0-1.
    protected double normalizeTotalError(double totalError){
        return totalError / getNrOfInstances();
    }
    //Overide this metohd to implement you own parsimony pressure.
    protected double calcParismonyPressure(GP gp){
        double pressure=0;        
        if(gp.getNrOfNodes()>options.getMAX_TREE_SIZE()){
            pressure += (gp.getNrOfNodes()-options.getMAX_TREE_SIZE())/2;
        }
        pressure +=    gp.getNrOfNodes()*options.getPUNISHMENT_FOR_LENGTH();
        return pressure;
    }
//----------------------------Evoltution CallBack functions-----------------------------------------

    public void evolutionStarted(EvolutionProcess source) {
     //   orgPunishmentForLength =  options.getPUNISHMENT_FOR_LENGTH();
    }
    private double orgPunishmentForLength;
    public void generationTerminated(EvolutionProcess source) {
     /*   if(alternatePressure){
            if(source.getCurrentGeneration()%2==0)
                options.setPUNISHMENT_FOR_LENGTH(1);
            else
                options.setPUNISHMENT_FOR_LENGTH(0.1);//*/
          //  options.setPUNISHMENT_FOR_LENGTH(options.getPUNISHMENT_FOR_LENGTH()==0?.1:0);            
       // }
    }

    public void batchTerminated(EvolutionProcess source) {
    }

    public void evolutionTerminated(EvolutionProcess source) {
      //  options.setPUNISHMENT_FOR_LENGTH(orgPunishmentForLength);
    }

    public void evolutionProcessTerminated(EvolutionProcess source) {
    }

    
//----------------------------Helper functions--------------------------------------------------    
    
    protected double getTargetValue(double[] instance){
        return instance[instance.length-1];
    }
    
    protected double getNrOfInstances(){
        return pc.values().size();
    }
    
    Integer nrOfTargetCategories;
    protected int getNrOfTargetCategories(){
        if(nrOfTargetCategories==null){
            Set<Double> targetClasses = new HashSet<Double>();
            Collection<Prediction> preds = pc.values();
            for(Prediction p:preds){                
                targetClasses.add(p.getTargetValue());
            }
            nrOfTargetCategories = targetClasses.size();
        }
        return nrOfTargetCategories;
    }
    
    private Double totalActualValue;
    protected double getTotalActualValue(){
        if(totalActualValue==null){
            double total = 0;
            Enumeration<double[]> instances = pc.keys();
            while(instances.hasMoreElements()){
                double[] instance = instances.nextElement();
                total+= getTargetValue(instance);
            }
            totalActualValue = new Double(total);
            
        }
        return totalActualValue;
    }
    
    Double averageActualValue;
    protected double getAverageActualValue(){
        if(averageActualValue==null){
            averageActualValue = new Double(getTotalActualValue()/getNrOfInstances());
        }
        return averageActualValue;
    }
    public String getName(){
        return name;
    }

    /**
     * @return the alternatePressure
     */
    public boolean isAlternatePressure() {
        return alternatePressure;
    }

    /**
     * @param alternatePressure the alternatePressure to set
     */
    public void setAlternatePressure(boolean alternatePressure) {
        this.alternatePressure = alternatePressure;
    }

}
